declare type CompilerOptions = {
  warn?: Function; // allow customizing warning in different environments; e.g. node
  expectHTML?: boolean; // only false for non-web builds
  modules?: Array<ModuleOptions>; // platform specific modules; e.g. style; class
  staticKeys?: string; // a list of AST properties to be considered static; for optimization
  directives?: { [key: string]: Function }; // platform specific directives
  isUnaryTag?: (tag: string) => ?boolean; // check if a tag is unary for the platform
  canBeLeftOpenTag?: (tag: string) => ?boolean; // check if a tag can be left opened
  isReservedTag?: (tag: string) => ?boolean; // check if a tag is a native for the platform
  mustUseProp?: (tag: string, type: ?string, name: string) => boolean; // check if an attribute should be bound as a property
  isPreTag?: (attr: string) => ?boolean; // check if a tag needs to preserve whitespace
  getTagNamespace?: (tag: string) => ?string; // check the namespace for a tag
  transforms?: Array<Function>; // a list of transforms on parsed AST before codegen
  preserveWhitespace?: boolean;
  isFromDOM?: boolean;
  shouldDecodeTags?: boolean;
  shouldDecodeNewlines?: boolean;

  // for ssr optimization compiler
  scopeId?: string;

  // runtime user-configurable
  delimiters?: [string, string]; // template delimiters

  // allow user kept comments
  comments?: boolean
};

declare type CompiledResult = {
  ast: ?ASTElement;
  render: string;
  staticRenderFns: Array<string>;
  stringRenderFns?: Array<string>;
  errors?: Array<string>;
  tips?: Array<string>;
};

declare type ModuleOptions = {
  preTransformNode: (el: ASTElement) => void;
  transformNode: (el: ASTElement) => void; // transform an element's AST node
  postTransformNode: (el: ASTElement) => void;
  genData: (el: ASTElement) => string; // generate extra data string for an element
  transformCode?: (el: ASTElement, code: string) => string; // further transform generated code for an element
  staticKeys?: Array<string>; // AST properties to be considered static
};

declare type ASTModifiers = { [key: string]: boolean };
declare type ASTIfConditions = Array<{ exp: ?string; block: ASTElement }>;

//示例：
//@click='hello'
declare type ASTElementHandler = {
  value: string; //事件回调函数的函数体部分。 如上述示例的'hello'
  modifiers: ?ASTModifiers; //比如@click.prevent，则该属性中存放着{prevent:true}，点号被删除了
};

declare type ASTElementHandlers = {
  [key: string]: ASTElementHandler | Array<ASTElementHandler>;
};

//例子：v-focus:hello.haha='world'
declare type ASTDirective = {
  name: string; //指focus
  rawName: string; //指v-focus:hello
  value: string; //指world
  arg: ?string;//指hello
  modifiers: ?ASTModifiers;//指haha处理之后的对象形式，这里为{haha:true}
};

declare type ASTNode = ASTElement | ASTText | ASTExpression;

declare type ASTElement = {
  type: 1;
  tag: string; //标签名，如果标签名中存在命名空间，tag属性中也会存储命名空间。
  //解析出的最原始的标签的属性的键值对，注意，某些数据在处理过程中会从数组中删除掉，比如v-if、v-for等，
  //也就是说，该数组中存储的属性在parser处理之后，可能保存的不是完整的dom的特性，而attrsMap保存的是完整的。
  attrsList: Array<{ name: string; value: string }>;
  //将attrsList中的每个对象转换为对象的键值对形式，
  //attrsMap主要是一种性能优化，用于根据属性名快速找到属性值，
  //并且对于attrsList来说，某些数据在处理过程中会从数组中删除掉，比如v-if、v-for等，但是
  //在attrsMap中不会被删除掉，也就是说attrsMap总是保存着完整的dom的特性列表
  attrsMap: { [key: string]: string | null };
  parent: ASTElement | void; //当前元素的父元素
  //当前元素的所有子元素，不包括作用域插槽的子元素
  children: Array<ASTNode>;

  //标记当前元素是否为静态的，也就是说，不需要每次都刷新的、与数据无关的内容，
  //而且本人发现该属性只在optimizer.js中使用，也就是为了获得staticRoot属性的值，
  //在除该文件外的其他位置暂未发现使用该属性
  static?: boolean;
  //首先当前元素的static属性必须为true，然后该元素必须存在子元素，并且子元素中不可只有一个文本元素，此时标记
  //staticRoot属性为true，该属性是在生产render字符串时，真正使用的判断静态节点的属性，static未使用。
  staticRoot?: boolean;
  //如果当前节点的static属性或者once属性为true，并且祖先节点（不包括当前节点，也就是说，
  //即使当前节点存在v-for，staticInFor也不会被标记为true）存在v-for指令，则staticInFor为true
  staticInFor?: boolean;
  //如果当前节点为静态根节点，并且在生成render字符串时，已经处理过该节点，则会标记该属性为true，
  //防止无限循环
  staticProcessed?: boolean;
  hasBindings?: boolean; //标记当前元素是动态元素，只要当前元素的某个属性的键的开头为^v-|^@|^:，该属性就会被标记为true

  text?: string;
  //存放元素或者组件的特性的数组，这里存储的属性名与值是经过处理的最终会被用来创建vnode
  //的特性数组。
  //示例： <div id="hello"></div>
  //在attrsList存储的结果为：[{name:'id', value:'hello'}]，
  //而在attrs中存储的结果为：[{name:'id', value:'"hello"'}]
  //注意在attrs中的value属性的值已经被处理过了。
  attrs?: Array<{ name: string; value: string }>;
  //存放dom属性的数组，在虚拟dom的data对象中，该属性会被转换成虚拟dom的domProps属性
  //对于组件标签来说，一般不会将属性放入props数组中，只会放入attrs中，只在一种情况下，会将组件的属性放入
  //props数组中，首先该组件不能设置is属性，并且属性必须通过v-bind修饰，而且必须添加.prop修饰，也就是v-bind.prop:hello="man"
  //对于原生标签，属性会被放入该数组的情况就是，首先该组件不能设置is属性，并且属性可以通过v-bind.prop修饰或者满足platformMustUseProp方法
  //所以说，不论对于组件标签还是原生标签来说，大部分属性都是在attrs数组中存储的。
  props?: Array<{ name: string; value: string }>;
  //要了解该属性的含义，就首先需要知道该属性的作用，该属性就是用于标记当前元素
  //在codegen节点不需要生成data对象（也就是createElement的第二个参数）
  //所以如果当前元素上没有任何属性，并且其子元素不存在任何作用域插槽，就会为标记为true
  plain?: boolean;
  //如果元素通过v-pre指令修饰，则pre属性为true，该属性只会在设置了v-pre指令的
  //元素上设置为true，其子元素不会设置pre属性为true。
  //pre属性主要是在optimizer优化阶段使用，用于判断当前节点是否为静态节点
  pre?: true;
  ns?: string;  //保存可能存在的命名空间

  component?: string; //通过is属性动态绑定组件，该属性用于存放is属性对应的值
  inlineTemplate?: true; //如果元素添加了inline-template属性，则该属性为true
  transitionMode?: string | null;
  //如果当前元素为slot元素，则该属性存放着可能存在的name属性的值，
  //比如：<slot name="hello"></slot>，则slotName为'hello'
  slotName?: ?string;
  //对于组件内部的插槽，可以存在一个slot属性，
  //该属性用于存储slot属性对应的值
  slotTarget?: ?string;
  //作用域插槽中，组件内部的插槽中的scope属性的值
  slotScope?: ?string;
  //保存着当前元素的子元素的作用域插槽，也就是template元素。
  //比如<div><template slot="hello" scope="prop"></template></div>
  //则对于div元素来说，子元素template会出现在div元素的抽象语法树的scopedSlots对象中，
  //而不会出现在children数组中。另外，键为：'"hello"'（注意双引号），值为template元素的AST
  scopedSlots?: { [name: string]: ASTElement };

  //在下面的关于ref的注释中，都会使用如下的实例：ref="hello"
  ref?: string; //指'hello'
  refInFor?: boolean; //只要当前元素设置了ref属性并且当前元素及其任意祖先元素设置了v-for，该属性都为true

  //在下面的关于v-if的注释中，都会使用如下的实例：v-if="isOk" v-else v-if-else="isNo"
  if?: string; //指'isOk'
  ifProcessed?: boolean;
  elseif?: string; //指'isNo'
  else?: true; //如果标签存在v-else，则该属性就为true
  //猜测：该数组用于存放当前元素可能存在的if else-if else的结果，
  //以及可能存在的下一个元素的else-if else的结果。比如：
  //<div id="one" v-if="1"></div>
  //<div id="two" v-else-if="2"></div>
  //<div id="three" v-else-if="3"></div>
  //<div id="four" v-else></div>
  //则对于one元素生成的ASTElement的ifConditions中，会保存one、two、three和four节点，
  //而two、three和four节点不会出现在父节点的children数组中，只有one节点会出现在父节点的children数组中
  ifConditions?: ASTIfConditions;

  //在下面的关于v-for的注释中，都会使用如下的实例：v-for="(item, key, index) in items"
  for?: string; //指'items'
  forProcessed?: boolean;
  //元素中key属性的值，注意，因为可以使用v-bind指令对key进行处理，
  //所以解析结果可能为原始属性值或者过滤器形式的字符串（对于这种形式的字符串，可
  //参考compiler/parser/filter-parser.js中的源码及注释
  key?: string;
  alias?: string; //指'item'
  iterator1?: string; //指'key'
  iterator2?: string; //指'index'

  //在下面的关于class和style的绑定的注释中，
  //都会使用如下的实例：<div class="hello" :class="world" style="color:red;" :style="{position:p}"></div>
  ////指'hello'， 静态的class，但是会被转换为'"hello"'的形式
  staticClass?: string;
  classBinding?: string; //指'world'，动态绑定的class
  //指'color:red;'，但是会将其转换为'{"color":"red"}'的形式
  staticStyle?: string;
  styleBinding?: string; //指'{position:p}'
  //示例：v-on:hello.capture="world"
  //键为：'!hello'，其中感叹号!是.capture修饰符转换的，
  //.capture：'!'
  //.once：'~'
  //.passive：'&'
  //该属性在转换成虚拟dom.data的属性时，会转换为on属性
  events?: ASTElementHandlers; //存放未使用.native修饰的事件，可用于任意元素或组件。
  //上述events属性的实例同样适用于nativeEvents
  //该属性在转换成虚拟dom.data的属性时，会转换为nativeOn属性
  nativeEvents?: ASTElementHandlers; //在绑定事件时，使用了.native修饰符，具体作用，可查看文档，该修饰符只在组件中使用。

  transition?: string | true;
  transitionOnAppear?: boolean;

  //在生成render函数字符串，处理v-model指令时，会设置该属性，
  //位置：compilers/directives/model.js中
  //实例：v-model="hello.world['go']"
  model?: {
    value: string; //指'(hello.world['go'])'
    callback: string; //其实该回调函数的含义就是为'hello.world['go']'形式的表达式赋值，具体可查看源码
    expression: string; //指'"hello.world['go']"'，注意双引号
  };

  //存放当前元素未处理的指令，比如自定义指令以及
  //v-model、v-show、v-html、v-text、v-cloak等
  //以及v-on="$listener" v-bind="$attrs"形式的属性绑定，
  //因为此时v-on或者v-bind都没有name（也就是v-on:click中的click），
  //所以会被添加到directives数组中，详情可见compiler\parser\index.js中的processAttrs方法中
  //关于指令解析的源码和注释
  directives?: Array<ASTDirective>;

  //如果当前元素是<style>或<script>，则该属性为true，
  //如果该属性为true，则不会将该元素放入树中，也就是不会放入父元素的children数组中，
  //这样该元素就不会被处理
  forbidden?: true;
  once?: true;//只要标签设置了v-once，该属性就为true
  onceProcessed?: boolean;
  //通过compilers/directives/bind.js添加，用于处理v-bind="$attrs"形式的属性绑定
  //注意v-bind没有参数（如v-bind:hello中的hello）
  //该属性是一个函数，详情查看bind.js的源码和注释
  wrapData?: (code: string) => string;
  //通过compilers/directives/on.js添加，用于处理v-on="$listener"形式的事件绑定，
  //注意v-on没有参数（如v-on:click中的click）
  //该属性是一个函数，该函数返回一个字符串，该字符串会被拼接成render函数，
  //用于在运行时将$listener对象中的所有事件扩展到astEl.on属性上，详情可查看on.js的源码和注释
  wrapListeners?: (code: string) => string;

  // 2.4 ssr optimization
  ssrOptimizability?: number;

  // weex specific
  appendAsTree?: boolean;
};

//示例：
//<div>你好，{{name}}，欢迎</div>
declare type ASTExpression = {
  type: 2;
  //值为：'"你好，" + _s(name) + "，欢迎"'
  //经过处理以后的字符串的值
  expression: string;
  //值为：'你好，{{name}}，欢迎'
  //最原始的字符串的值，此属性未被使用过
  text: string;
  static?: boolean; //总是为false
  // 2.4 ssr optimization
  ssrOptimizability?: number;
};

declare type ASTText = {
  type: 3;
  text: string;
  static?: boolean; //总是为true
  isComment?: boolean; //当前文本是否是注释，true表示是注释
  // 2.4 ssr optimization
  ssrOptimizability?: number;
};

// SFC-parser related declarations

// an object format describing a single-file component.
declare type SFCDescriptor = {
  template: ?SFCBlock;
  script: ?SFCBlock;
  styles: Array<SFCBlock>;
  customBlocks: Array<SFCCustomBlock>;
}

declare type SFCCustomBlock = {
  type: string;
  content: string;
  start?: number;
  end?: number;
  src?: string;
  attrs: { [attribute: string]: string };
};

declare type SFCBlock = {
  type: string;
  content: string;
  start?: number;
  end?: number;
  lang?: string;
  src?: string;
  scoped?: boolean;
  module?: string | boolean;
};
